﻿/*Copyright (c) 2012 Cesar Ramirez
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
 * documentation files (the "Software"), to deal in the Software without restriction, including without limitation 
 * the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 
 * and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in all copies or substantial 
 * portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
 * LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 
 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.*/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Net;
using MechTactics.GameElements;
using System.IO;

namespace MechTactics
{
    public class Server
    {
        private Launcher.updateTxtDisplayCallback updateText;
        private Launcher.updateButtonStateCallback buttonState;
        private Launcher.updateScoreCallback updateScore;

        private List<Client> clients;
        private TcpListener listener;
        private StreamWriter writer;
        private StreamReader reader;
        private int numberOfPlayers;
        private KeyValuePair<int, int>[] initialPositions;

        public Simulator sim;
        private int turn = 1;
        private bool stop = false;
        private bool socketOpen = false;
        private bool saving = false;
        
        public Server(int numberOfPlayers)
        {
            this.numberOfPlayers = numberOfPlayers;
            this.sim = new Simulator();
        }

        public void setMap(int[,] p, int maxHeight, KeyValuePair<int, int>[] initialPositions)
        {
            this.sim.setMap(new TileMap(p), maxHeight);
            this.initialPositions = initialPositions;
        }

        public bool startListening() 
        {
            socketOpen = true;
            clients = new List<Client>(0);                     
            listener = new TcpListener(IPAddress.Loopback, 4654);   
            listener.Start();
            string savedGame = DateTime.Now.ToString("hh.mm.ss") + ".msg";
            writer = new StreamWriter(savedGame);
            writer.AutoFlush = true;
            for (int i = 0; i < numberOfPlayers; i++)
            {
                Client client = new Client(listener, i); 
                if (client.isConnected)
                {
                    this.updateText("Client Added");
                    clients.Add(client);
                }
            }
            listener.Stop();
            foreach (Client client in clients)                          
            {
                sim.addPlayer(client.Player);                          
            }
            if (clients.Count != numberOfPlayers)
            {
                this.updateText("Error while connecting to clients");
                return false;
            }
            this.updateText("All client added");
            saving = true;
            return true;           
        }

        public void startLoadingData(string filePath)
        {
            saving = false;
            reader = new StreamReader(filePath);
            clients = new List<Client>(0);
            string mapData = loadData();
            TileMap map = Serializer.fromStringToMap(mapData);
            for (int i = 0; i < numberOfPlayers; i++)
            {
                Client client = Serializer.fromStringToStats(loadData());
                clients.Add(client);
                client.Player.map = map;
                client.Player.InitGame();
                sim.addPlayer(client.Player); 
            }
            sim.setMap(map, 10);
        }

        public void startGame()
        {
            stop = false;
            this.updateText("Sending initial data");
            sendInitialData();
            this.updateText("Starting the game");
            gameLoop();
            this.updateText("Closing connections");
            closeConnections();
        }

        private void sendInitialData()
        {
            if (saving)
            {
                saveData(Serializer.fromMapToString(sim.map));
                foreach (Client client in clients)
                {
                    client.sendString("Data");
                    client.Player.setInitialParams(initialPositions.ElementAt(client.id), sim.STARTING_ORE, sim.map);
                    client.sendString(Serializer.fromStatstoString(client.id, sim.STARTING_ORE, initialPositions.ElementAt(client.id), sim.MAX_TURN));
                    saveData(Serializer.fromStatstoString(client.id, sim.STARTING_ORE, initialPositions.ElementAt(client.id), sim.MAX_TURN));
                    client.sendString(Serializer.fromMapToString(sim.map));
                    client.Player.InitGame();
                    if (client.recieveString().Equals("run"))
                    {
                        this.updateText("Data sent");
                    }
                    else
                    {
                        this.updateText("Error while sending data to client");
                        client.disconectFromServer();
                    }
                }
            }
            else
            {
                this.updateText("Not in connection mode, data already loaded");
            }
        }

        private void saveData(string data)
        {
                writer.Write(StringCompressor.CompressString(data) + ";");
        }

        private string loadData()
        {
            string temp = "";

            while (true)
            {
                int charFound = reader.Read();
                char singleChar = Convert.ToChar(charFound);
                if (singleChar.Equals(';'))
                    break;
                else
                    temp = temp + singleChar;
            }
            temp = StringCompressor.DecompressString(temp);
            temp.Replace("\n", "");
            return temp;
        }

        private void gameLoop()
        {
            turn = 1;
            this.updateText("Starting game loop");
            int activePlayers = numberOfPlayers;
            sim.running = true;
            sim.turnResult = 0;

            //For debbuging
            long endTime;
            long startTime;

            while (true && !stop)
            {
                this.updateText("Turn " + turn);
                foreach (Client client in clients)
                {
                    if (client.isConnected)
                    {
                        if (client.Player.isActive)
                        {
                            startTime = System.DateTime.Now.Millisecond;
                            this.updateText("Client " + client.id + ":");
                            client.Player.startTurn();
                            string snapshot = Serializer.fromElementsToString(sim.map.elements, client.Player.ore);
                            try
                            {
                                this.updateText("Sending turn data");
                                if (saving)
                                    client.sendString(snapshot);
                            }
                            catch (Exception e)
                            {
                                this.updateText("Error while sending data");
                                client.isConnected = false;
                                activePlayers--;
                                continue;
                            }
                            endTime = System.DateTime.Now.Millisecond;

                            this.updateText("Time Sending Data: " + (endTime - startTime));

                            string action = "";
                            try
                            {
                                startTime = System.DateTime.Now.Millisecond;
                                this.updateText("Recieving command");
                                if (saving)
                                {
                                    action = client.recieveString();
                                    saveData(action);
                                }
                                else
                                    action = loadData();
                                this.updateText("Command recieved");
                                endTime = System.DateTime.Now.Millisecond;
                                this.updateText("Time Waiting for Data: " + (endTime - startTime));

                                sim.readable = false;
                                startTime = System.DateTime.Now.Millisecond;
                                if (Constants.SINGLE_COMMAND)
                                {
                                    Command toExecute = Serializer.fromStringToCommand(action);
                                    if (toExecute.playerId == client.id)
                                    {
                                        this.updateText("Command sent to simulator");
                                        sim.executeCommand(toExecute);
                                    }
                                    else
                                    {
                                        this.updateText("Invalid command");
                                    }
                                }
                                else
                                {
                                    List<Command> commandList = Serializer.fromStringToCommandList(action);
                                    foreach (Command toExecute in commandList)
                                    {
                                        if (toExecute.playerId == client.id)
                                        {
                                            sim.executeCommand(toExecute);
                                        }
                                        else
                                        {
                                            this.updateText("Invalid command");
                                        }
                                    }
                                    this.updateText("Command List sent to simulator");
                                }
                                sim.readable = true;
                                endTime = System.DateTime.Now.Millisecond;
                                this.updateText("Time Excecuting Data: " + (endTime - startTime));                                
                            }
                            catch (Exception e)
                            {
                                this.updateText("Error while recieving data");
                                client.isConnected = false;
                                activePlayers--;
                                continue;
                            }

                            client.Player.endTurn();

                            switch (sim.turnResult)
                            {
                                case 0:
                                    break;
                                case 1:
                                    activePlayers--;
                                    break;
                                case -1:
                                    client.isConnected = false;
                                    activePlayers--;
                                    break;
                            }
                            this.updateText("Turn completed");
                            updateScore(client.Player.playerNumber, client.Player.ore, client.Player.objectList.Count, client.Player.score);
                            sim.flush();
                        }
                    }
                }
                if (activePlayers <= (1) || turn >= sim.MAX_TURN)
                {
                    break;
                }
                turn++;
            }
        }

        private void closeConnections()
        {
            if (socketOpen)
            {
                for (int i = 0; i < clients.Count; i++ )
                    {
                        Client client = clients.ElementAt(i);
                        client.disconectFromServer();
                        client.connection.tcp.Client.Dispose();
                        client.connection.tcp.Close();
                        client.connection.tcp = null;
                    }
                this.listener.Stop();
                if (writer != null)
                {
                    writer.Flush();
                    writer.Close();
                }
            }
            if (!saving)
            {
                if (reader != null)
                {
                    reader.Close();
                }
            }
            this.buttonState(false);
            socketOpen = false;
        }

        public void stopServer()
        {
            stop = true;
            closeConnections();
        }

        public void logMessage(Launcher.updateTxtDisplayCallback updateText)
        {
            this.updateText = updateText;
        }

        public void serverRunning(Launcher.updateButtonStateCallback buttonState)
        {
            this.buttonState = buttonState;
        }

        public void newScore(Launcher.updateScoreCallback newScore)
        {
            this.updateScore = newScore;
        }
    }
}
